﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using CFScript.PInvoke;
using System.IO;

namespace CFScript.Tasks
{
    /// <summary>
    /// Kill class implements CFScript task &lt;kill&gt;. Given a process name, fullpath or PID, this task will
    /// try to kill the associated process (if found).
    /// </summary>
    class Kill: Task
    {
        /// <summary>
        /// Class constructor
        /// </summary>
        public Kill()
        {
            // Setup task configuration
            this.name = "kill";
            this.parameterNames = new string[] {"fullpath", "name", "pid"};
            this.parameterExpandable = new bool[] { true, true, false};
            this.parameterRequired = new bool[] {false, false, false};
            this.parameters = new Dictionary<string, Parameter>();

            Debug.Assert(this.parameterNames.Length == this.parameterRequired.Length &&
                         this.parameterRequired.Length == this.parameterRequired.Length);
        }

        /// <summary>
        /// Task execution method. This supports 3 attributes, but just one at the same time.
        /// When 'fullpath' attribute is used, the task will try to find and kill a process running with the given path.
        /// When 'name' attribute is used, the task will try to find and kill all processes running with the same name. Extension is optional.
        /// When 'pid' attribute is used, the task will try to find and kill a process running with the given path.
        /// If no process is killed because it is not running, returned object will have a ResultCode.WARN value.
        /// </summary>
        /// <param name="context"></param>
        /// <returns>Returns an InstructionResult instance object with the result of the execution</returns>
        /// <exception cref="MissingRequiredParameterException">Thrown when no attribute is present. The task requieres at least one of 'fullpath', 'name' or 'pid'</exception>
        /// <exception cref="ScriptExecutioException">Thrown when more than one attribute is present. The task supports just one attribute of 'fullpath', 
        /// 'name' or 'pid' at the same time</exception>
        public override InstructionResult Execute(CFScript context)
        {
            InstructionResult result = new InstructionResult();

            // Attributes are mutually exclusive.
            if (this.parameters.Count == 0)
            {
                throw new MissingRequiredParameterException("Parameter 'fullpath', 'name' or 'pid' required");
            }
            else if(this.parameters.Count > 1)
            {
                throw new ScriptExecutionException("<kill> task only allows a parameter at the same time. Use just one of 'fullpath', 'name' or 'pid'");
            }
            else if (this.parameters.ContainsKey("fullpath"))
            {
                // Kill process by fullpath
                string fullpath = this.parameters["fullpath"].GetValue(context);

                try
                {
                    if (ProcessCE.FindAndKill(fullpath))
                    {
                        result.Code = ResultCode.OK;
                    }
                    else
                    {
                        result.Code = ResultCode.WARN;
                        switch (context.Options.VerboseLevel)
                        {
                            case VerboseMode.MINIMUM:
                            case VerboseMode.ALL:
                                result.Message = string.Format("Process {0} not found or couldn't be killed", fullpath);
                                break;
                        }
                    }
                }
                catch(Exception ex)
                {
                    result.Code = ResultCode.ERROR;
                    result.Message = ex.Message;
                }

            }
            else if (this.parameters.ContainsKey("name"))
            {
                // Kill process by name
                string name = this.parameters["name"].GetValue(context);

                try
                {
                    ProcessInfo[] runningProcesses = ProcessCE.GetProcesses();
                    int processesKilled = 0;

                    foreach (ProcessInfo pi in runningProcesses)
                    {
                        string piFullpath = pi.FullPath;
                        string piName = Path.GetFileName(piFullpath);
                        string piNameWOExtension = Path.GetFileNameWithoutExtension(piFullpath);

                        if (name.ToLower() == piName.ToLower() || name.ToLower() == piNameWOExtension.ToLower())
                        {
                            pi.Kill();
                            processesKilled++;
                        }
                    }

                    if (processesKilled == 0)
                    {
                        result.Code = ResultCode.WARN;
                        switch (context.Options.VerboseLevel)
                        {
                            case VerboseMode.MINIMUM:
                            case VerboseMode.ALL:
                                result.Message = string.Format("Processes with name {0} not found", name);
                                break;
                        }
                    }
                    else
                    {
                        result.Code = ResultCode.OK;
                        result.Message = string.Format("{0} process(es) with name {1} killed", processesKilled, name);
                    }
                }
                catch (Exception ex)
                {
                    result.Code = ResultCode.ERROR;
                    result.Message = ex.Message;
                }
            }
            else
            {   // phobeous: I guess this task with param. 'pid' won't be very useful but...
                // Kill process by PID
                int pid = this.parameters["pid"].GetValueAsInteger();

                try
                {
                    Process processById = Process.GetProcessById(pid);

                    if (processById != null)
                    {
                        processById.Kill();
                        result.Code = ResultCode.OK;
                    }
                    else
                    {
                        // PID not running. Can't kill it
                        result.Code = ResultCode.WARN;
                        switch (context.Options.VerboseLevel)
                        {
                            case VerboseMode.MINIMUM:
                            case VerboseMode.ALL:
                                result.Message = string.Format("Process with PID {0} not found", pid);
                                break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    result.Code = ResultCode.ERROR;
                    result.Message = ex.Message;
                }
            }

            result.BreakExecution = result.Code == ResultCode.ERROR && this.options.OnError == OnErrorAction.BREAK;

            return result;
        }
    }
}
